package com.gitee.wsl.android.ext

import android.content.Context
import android.graphics.*
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.*
import android.text.style.*
import android.view.View
import androidx.annotation.*
import androidx.core.text.inSpans
import androidx.core.text.set
import com.gitee.wsl.android.ext.ApplicationInit.application
import com.gitee.wsl.android.ext.SpannableStringBuilderExt.BOOLEAN_FALSE_INDEX_OFFSET
import com.gitee.wsl.android.ext.SpannableStringBuilderExt.BOOLEAN_TRUE_INDEX_OFFSET
import com.gitee.wsl.android.ext.SpannableStringBuilderExt.IMAGE_SPAN_TEXT
import com.gitee.wsl.android.ext.SpannableStringBuilderExt.applyColoredSpannable
import com.gitee.wsl.android.ext.SpannableStringBuilderExt.applyColoredSpannableSubstring
import com.gitee.wsl.ext.string.TokenType
import com.gitee.wsl.ext.base.isFalse
import com.gitee.wsl.ext.collection.loop
import com.gitee.wsl.ext.string.indexOfNextToken
import com.gitee.wsl.ext.string.indexOfNextUnescapedQuote
import org.json.JSONObject
import timber.log.Timber
import java.util.Locale
import java.util.regex.Matcher
import java.util.regex.Pattern


fun sampleSpannable(builderConfig: SpannableScope.()->Unit):SpannableStringBuilder{
    val newSpannableStringBuilder = SpannableStringBuilderAdv()
    builderConfig(newSpannableStringBuilder)
    return newSpannableStringBuilder
}

object SpanExt{
    /**
     * get the positions of '\n' from SpannableStringBuilder from start to end
     *
     * @param ssb   the content
     * @param start the start position
     * @param end   the end position
     * @return the '\n' positions
     */
    fun getNewLineCharPosition(ssb: SpannableStringBuilder, start: Int, end: Int): List<Int> {
        val list: MutableList<Int> = ArrayList()
        for (i in start until end) {
            if (ssb[i] == '\n') {
                list.add(i)
            }
        }
        return list
    }

}

interface SpannableReplaceAtScope{
      val startIndex:Int
      val endIndex:Int
      val spannableScope: SpannableBlockScope
}

interface SpannableReplaceScope{

    fun at(start: Int, end: Int,spans: SpannableReplaceAtScope.()->Unit)

    fun at(vararg str:String,start: Int=0,spans: SpannableReplaceAtScope.()->Unit)

    /**
     * 设置Span文字效果
     * @param what 文字效果, 如果为数组或者集合则设置多个
     * @param start 开始索引
     * @param end 结束索引
     * @param flags 参考 [Spanned.SPAN_EXCLUSIVE_EXCLUSIVE]
     *
     */
   fun setOrReplaceSpan(
       what: Any,
       start: Int=0,
       end: Int,
       flags: Int = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
   )

    fun replaceSpan(
        oldValue: String,
        startIndex: Int = 0,
        endIndex: Int? = null,
        ignoreCase: Boolean = false,
        replacement: (MatchResult) -> Any?
    ){
       val regex = if (ignoreCase) {
           Regex.escape(oldValue).toRegex(RegexOption.IGNORE_CASE)
       } else {
           Regex.escape(oldValue).toRegex()
       }
      replaceSpan(regex, startIndex = startIndex, replacement = replacement)
   }

    /**
     * 使用正则替换匹配字符串
     *
     * @param regex 正则
     * @param quoteGroup 是否允许反向引用捕获组
     * @param startIndex 从指定的索引开始
     * @param replacement 每次匹配到字符串都会调用此函数, 该函数返回值可以是[Spanned]或[Spanned]数组/集合或[CharSequence], 空字符则无效
     * 1. 如果返回null则表示不执行任何操作
     * 2. 返回单个Span则应用效果, 当然返回Span集合或数组就会应用多个效果,
     * 3. 返回[Spanned]可以替换字符串同时添加Span效果.
     * 4. 返回[CharSequence]则仅仅是替换字符串.
     * 5. 并且本函数支持反向引用捕获组, 使用方法等同于RegEx: $捕获组索引
     * 6. 和[replace]函数不同的是本函数会保留原有[Spanned]的效果
     *
     */
    fun replaceSpan(
        regex: Regex,
        startIndex: Int = 0,
        endIndex: Int? = null,
        quoteGroup: Boolean = false,
        replacement: (MatchResult) -> Any?
    )

    fun replaceSpanFirst(
        oldValue: String,
        ignoreCase: Boolean = false,
        startIndex: Int = 0,
        replacement: (MatchResult) -> Any?
    ){
        val regex = if (ignoreCase) {
            Regex.escape(oldValue).toRegex(RegexOption.IGNORE_CASE)
        } else {
            Regex.escape(oldValue).toRegex()
        }
        replaceSpanFirst(regex, startIndex = startIndex, replacement = replacement)
    }

    fun replaceSpanFirst(
        regex: Regex,
        quoteGroup: Boolean = false,
        startIndex: Int = 0,
        replacement: (MatchResult) -> Any?
    )


    /**
     * 使用正则替换第一个匹配字符串
     *
     * @param regex 正则
     * @param quoteGroup 是否允许反向引用捕获组
     * @param startIndex 从指定的索引开始
     * @param replacement 每次匹配到字符串都会调用此函数, 该函数返回值可以是[Spanned]或[Spanned]数组/集合或[CharSequence], 空字符则无效
     * 1. 如果返回null则表示不执行任何操作
     * 2. 返回单个Span则应用效果, 当然返回Span集合或数组就会应用多个效果,
     * 3. 返回[Spanned]可以替换字符串同时添加Span效果.
     * 4. 返回[CharSequence]则仅仅是替换字符串.
     * 5. 并且本函数支持反向引用捕获组, 使用方法等同于RegEx: $捕获组索引
     * 6. 和[replace]函数不同的是本函数会保留原有[Spanned]的效果
     *
     */
    fun replaceSpanLast(
        oldValue: String,
        ignoreCase: Boolean = false,
        startIndex: Int = 0,
        replacement: (MatchResult) -> Any?
    ) {
        val regex = if (ignoreCase) {
            Regex.escape(oldValue).toRegex(RegexOption.IGNORE_CASE)
        } else {
            Regex.escape(oldValue).toRegex()
        }
        return replaceSpanLast(regex, startIndex = startIndex, replacement = replacement)
    }

    /**
     * 使用正则替换最后一个匹配字符串
     *
     * @param regex 正则
     * @param quoteGroup 是否允许反向引用捕获组
     * @param startIndex 从指定的索引开始
     * @param replacement 每次匹配到字符串都会调用此函数, 该函数返回值可以是[Spanned]或[Spanned]数组/集合或[CharSequence], 空字符则无效
     * 1. 如果返回null则表示不执行任何操作
     * 2. 返回单个Span则应用效果, 当然返回Span集合或数组就会应用多个效果,
     * 3. 返回[Spanned]可以替换字符串同时添加Span效果.
     * 4. 返回[CharSequence]则仅仅是替换字符串.
     * 5. 并且本函数支持反向引用捕获组, 使用方法等同于RegEx: $捕获组索引
     * 6. 和[replace]函数不同的是本函数会保留原有[Spanned]的效果
     *
     */
    fun replaceSpanLast(
    regex: Regex,
    quoteGroup: Boolean = false,
    startIndex: Int = 0,
    replacement: (MatchResult) -> Any?
    )

    fun clearSpans()

    fun appendSpan(builderConfig: SpannableBlockScope.()->Unit)

}


fun SpannableReplaceScope.color(@ColorInt color: Int, repStr:Regex, startIndex: Int = 0,
                                endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){ForegroundColorSpan(color)}
}



fun SpannableReplaceScope.color(@ColorInt color: Int, repStr:String, startIndex: Int = 0,
                                endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){ForegroundColorSpan(color)}
}

fun SpannableReplaceScope.backgroundColor(@ColorInt color: Int, repStr:Regex, startIndex: Int = 0,
                                          endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){BackgroundColorSpan(color)}
}

fun SpannableReplaceScope.backgroundColor(@ColorInt color: Int, repStr:String, startIndex: Int = 0,
                                          endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){
        BackgroundColorSpan(color)
    }
}


fun SpannableReplaceScope.scaleX(@FloatRange(from = 0.0) proportion: Float, repStr:Regex, startIndex: Int = 0,
                                 endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){
        ScaleXSpan(proportion)
    }
}

fun SpannableReplaceScope.scaleX(@FloatRange(from = 0.0) proportion: Float, repStr:String, startIndex: Int = 0,
                                 endIndex:Int? = null){
    replaceSpan(repStr,startIndex,endIndex){
        ScaleXSpan(proportion)
    }
}

fun SpannableReplaceAtScope.scaleX(@FloatRange(from = 0.0) proportion: Float){
    spannableScope[startIndex, endIndex] = ScaleXSpan(proportion)
}


inline fun CharSequence.spannable(config: SpannableReplaceScope.()->Unit):SpannedString{
   val scope = SpannableStringBuilderAdv(this)
   config(scope)
   return SpannedString(scope)
}

object SpannableStringBuilderExt {
    // corresponds to length of word 'true'
     const val BOOLEAN_TRUE_INDEX_OFFSET = 4

    // corresponds to length of word 'false'
     const val BOOLEAN_FALSE_INDEX_OFFSET = 5

    internal const val IMAGE_SPAN_TEXT = "<img/>"
    const val SPACE_SPAN_TEXT = "<space/>"

    fun applyColoredSpannable(
        text: SpannableScope,
        keyWords: List<String>,
        backgroundColor: Int,
        foregroundColor: Int
    ): SpannableScope {
        return text.apply {
            var matcher: Matcher
            keyWords.forEach {keyWord->
                matcher = Pattern.compile(keyWord).matcher(this)
                while (matcher.find()) {
                    applyColoredSpannableSubstring(this,matcher.start(),keyWord.length, backgroundColor, foregroundColor)
                }
            }
        }
    }

     fun applyColoredSpannable(
         text: SpannableScope,
         indexes: List<Int>,
         length: Int,
         backgroundColor: Int,
         foregroundColor: Int
    ): SpannableScope {
        return text.apply {
            indexes.forEach {
                applyColoredSpannableSubstring(text, it, length, backgroundColor, foregroundColor)
            }
        }
    }

   fun applyColoredSpannableSubstring(
       text: SpannableScope,
       subStringStartPosition: Int,
       subStringLength: Int,
       backgroundColor: Int,
       foregroundColor: Int
    ): SpannableScope {
        return text.apply {
            setSpan(
                UnderlineSpan(),
                subStringStartPosition,
                subStringStartPosition + subStringLength,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            setSpan(
                ForegroundColorSpan(foregroundColor),
                subStringStartPosition,
                subStringStartPosition + subStringLength,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            setSpan(
                BackgroundColorSpan(backgroundColor),
                subStringStartPosition,
                subStringStartPosition + subStringLength,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
        }
    }

    fun spanJson(input: CharSequence,jsonDigitsAndNullValueColor:Int,jsonSignElementsColor:Int): SpannableStringBuilder {
        // First handle the pretty printing step via gson built-in support
        val prettyPrintedInput = formatJson(input.toString())

        var lastTokenType: TokenType? = null
        var index = 0

        val sb = SpannableStringBuilderAdv(prettyPrintedInput)
        // First we set a span for all text to match the digits and null value color since other
        // cases will be overridden below
        sb.setColor(0, prettyPrintedInput.length, jsonDigitsAndNullValueColor)
        while (index < prettyPrintedInput.length) {
            val (tokenIndex, tokenType) = prettyPrintedInput.indexOfNextToken(startIndex = index)
            when (tokenType) {
                TokenType.BOOLEAN -> sb.setBooleanColor(tokenIndex).also { endIndex ->
                    index = endIndex
                }
                TokenType.ARRAY,
                TokenType.OBJECT,
                TokenType.KEY_SEPARATOR,
                TokenType.VALUE_SEPARATOR -> {
                    sb.setColor(
                        start = tokenIndex,
                        end = tokenIndex + 1,
                        color = jsonSignElementsColor
                    )
                    index = tokenIndex + 1
                }
                TokenType.STRING -> sb.setStringColor(tokenIndex, lastTokenType)?.also { endIndex ->
                    index = endIndex + 1
                } ?: return sb
                TokenType.NONE -> return sb
            }
            lastTokenType = tokenType
        }
        return sb
    }
}


/**
 * Wrap appended text in [builderAction] in a bold [StyleSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.bold(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(StyleSpan(Typeface.BOLD), builderAction = builderAction)

fun SpannableReplaceAtScope.bold(){
    spannableScope[startIndex, endIndex] = StyleSpan(Typeface.BOLD)
}

/**
 * Wrap appended text in [builderAction] in an italic [StyleSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.italic(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(StyleSpan(Typeface.ITALIC), builderAction = builderAction)

fun SpannableReplaceAtScope.italic(){
    spannableScope[startIndex, endIndex] = StyleSpan(Typeface.ITALIC)
}

/**
 * Wrap appended text in [builderAction] in an [UnderlineSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.underline(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(UnderlineSpan(), builderAction = builderAction)

fun SpannableReplaceAtScope.underline(){
    spannableScope[startIndex, endIndex] = UnderlineSpan()
}

/**
 * Wrap appended text in [builderAction] in a [ForegroundColorSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.color(
    @ColorInt color: Int,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(ForegroundColorSpan(color), builderAction = builderAction)

fun SpannableReplaceAtScope.color(@ColorInt color: Int){
    spannableScope[startIndex, endIndex] = ForegroundColorSpan(color)
}

/**
 * Wrap appended text in [builderAction] in a [BackgroundColorSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.backgroundColor(
    @ColorInt color: Int,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(BackgroundColorSpan(color), builderAction = builderAction)

fun SpannableReplaceAtScope.backgroundColor(@ColorInt color: Int){
    spannableScope[startIndex, endIndex] = BackgroundColorSpan(color)
}

/**
 * Wrap appended text in [builderAction] in a [StrikethroughSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.strikeThrough(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(StrikethroughSpan(), builderAction = builderAction)

fun SpannableReplaceAtScope.strikeThrough(){
    spannableScope[startIndex, endIndex] = StrikethroughSpan()
}

/**
 * Wrap appended text in [builderAction] in a [RelativeSizeSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.scale(
    proportion: Float,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(RelativeSizeSpan(proportion), builderAction = builderAction)

/**
 * Wrap appended text in [builderAction] in a [SuperscriptSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.superscript(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(SuperscriptSpan(), builderAction = builderAction)

fun SpannableReplaceAtScope.superscript(){
    spannableScope[startIndex, endIndex] = SuperscriptSpan()
}

/**
 * Wrap appended text in [builderAction] in a [SubscriptSpan].
 *
 * @see SpannableStringBuilder.inSpans
 */
fun SpannableScope.subscript(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(SubscriptSpan(), builderAction = builderAction)

fun SpannableReplaceAtScope.subscript(){
    spannableScope[startIndex, endIndex] = SubscriptSpan()
}

/**
 * Highlight parts of the String when it matches the search.
 *
 * @param search the text to highlight
 */
 fun SpannableScope.highlightWithDefinedColors(
    search: String,
    startIndices: List<Int>,
    backgroundColor: Int,
    foregroundColor: Int
   ): SpannableScope {
    return applyColoredSpannable(this, startIndices, search.length, backgroundColor, foregroundColor)
   }


 fun SpannableScope.highlightWithDefinedColorsSubstring(
    search: String,
    startIndex: Int,
    backgroundColor: Int,
    foregroundColor: Int
   ): SpannableScope {
    return applyColoredSpannableSubstring(this, startIndex, search.length, backgroundColor, foregroundColor)
  }


/*fun SpannableStringBuilder.text(text:String): SpannableStringBuilder {
    append(text)
    return this
}*/


fun SpannableScope.setColor(start: Int, end: Int, color: Int): SpannableScope {
    this.setSpan(
        ForegroundColorSpan(color),
        start,
        end,
        Spanned.SPAN_INCLUSIVE_INCLUSIVE
    )
    return this
}

/**
 * Given the tokenIndex, attempt to format the boolean value by first checking to see if the
 * token starts with a 't' for true or not.
 *
 * Returns the index of the end of the spanned boolean value
 */
private fun SpannableScope.setBooleanColor(tokenIndex: Int, jsonBooleanColor:Int= Color.CYAN): Int {
    val endIndex = if (this[tokenIndex].equals('t', ignoreCase = true)) {
        tokenIndex + BOOLEAN_TRUE_INDEX_OFFSET
    } else {
        tokenIndex + BOOLEAN_FALSE_INDEX_OFFSET
    }

    setColor( tokenIndex, endIndex,jsonBooleanColor)

    return endIndex
}

/**
 * Given the tokenIndex and lastTokenType, attempt to format the color string by searching for
 * the next unescaped quote mark in the string. If none is found, we return null immediately
 * which should signal that the JSON string is incomplete and all further formatting should stop.
 *
 * Otherwise, we will return the index of the end of the spanned string
 */
 fun SpannableScope.setStringColor(tokenIndex: Int, lastTokenType: TokenType? = null, jsonKeyColor:Int=Color.BLUE, jsonValueColor:Int=Color.BLUE): Int? {
    val color = when (lastTokenType) {
        TokenType.ARRAY,
        TokenType.OBJECT,
        TokenType.VALUE_SEPARATOR,
        TokenType.NONE,
        null -> {
            jsonKeyColor
        }
        else -> {
            jsonValueColor
        }
    }

    @Suppress("TooGenericExceptionCaught", "SwallowedException")
    val endIndex =
        try {
            this.indexOfNextUnescapedQuote(tokenIndex + 1)
        } catch (e: Exception) {
            -1
        }
    // if we somehow get an incomplete string, we lose the ability to parse any other
    // tokens, so just return now
    if (endIndex < tokenIndex) {
        return null
    }
    setColor(start = tokenIndex, end = endIndex + 1, color)



    return endIndex
}

operator fun Spannable.set(string: String, span: Any) =
    toString().indexOf(string).takeIf { it != -1 }?.let { start ->
        setSpan(span, start, start + string.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
    }

operator fun Spannable.set(string: String, spans: List<Any>) =
    toString().indexOf(string).takeIf { it != -1 }?.let { start ->
        spans.forEach { span -> setSpan(span, start, start + string.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) }
    }

fun SpannableScope.size(
    size: Float,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = size(size.toInt(), builderAction)

fun SpannableScope.size(
    size: Int,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(AbsoluteSizeSpan(size), builderAction)

fun SpannableReplaceAtScope.size(size: Int){
    spannableScope[startIndex, endIndex] = AbsoluteSizeSpan(size)
}


 fun SpannableScope.blur(
    radius: Float,
    style: BlurMaskFilter.Blur = BlurMaskFilter.Blur.NORMAL,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = maskFilter(BlurMaskFilter(radius, style), builderAction)

fun SpannableReplaceAtScope.blur(
    radius: Float,
    style: BlurMaskFilter.Blur = BlurMaskFilter.Blur.NORMAL){
    spannableScope[startIndex, endIndex] = MaskFilterSpan(BlurMaskFilter(radius, style))
}

 fun SpannableScope.maskFilter(
    filter: MaskFilter,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(MaskFilterSpan(filter), builderAction)

 fun SpannableScope.fontFamily(
    family: String,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(TypefaceSpan(family), builderAction)

fun SpannableReplaceAtScope.fontFamily(
    family: String){
    spannableScope[startIndex, endIndex] =TypefaceSpan(family)
}

 fun SpannableScope.url(
    url: String,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(URLSpan(url), builderAction)

fun SpannableReplaceAtScope.url(url: String){
    spannableScope[startIndex, endIndex] = URLSpan(url)
}

 fun SpannableScope.alignmentCenter(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = alignment(Layout.Alignment.ALIGN_CENTER, builderAction)

 fun SpannableScope.alignmentOpposite(
    builderAction: SpannableScope.() -> Unit
): SpannableScope = alignment(Layout.Alignment.ALIGN_OPPOSITE, builderAction)

 fun SpannableScope.alignment(
    alignment: Layout.Alignment,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(AlignmentSpan.Standard(alignment), builderAction)

fun SpannableReplaceAtScope.alignment(alignment: Layout.Alignment){
    spannableScope[startIndex, endIndex] = AlignmentSpan.Standard(alignment)
}

fun SpannableReplaceAtScope.alignmentCenter(){
    spannableScope[startIndex, endIndex] = AlignmentSpan.Standard( Layout.Alignment.ALIGN_CENTER)
}

fun SpannableReplaceAtScope.alignmentOpposite(){
    spannableScope[startIndex, endIndex] = AlignmentSpan.Standard( Layout.Alignment.ALIGN_OPPOSITE)
}

 fun SpannableScope.leadingMargin(
    first: Float,
    rest: Float = first,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = leadingMargin(first.toInt(), rest.toInt(), builderAction)

 fun SpannableScope.leadingMargin(
    first: Int,
    rest: Int = first,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(LeadingMarginSpan.Standard(first, rest), builderAction)

 fun SpannableScope.bullet(
    gapWidth: Float,
    @ColorInt color: Int? = null,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = bullet(gapWidth.toInt(), color, builderAction)

 fun SpannableScope.bullet(
    gapWidth: Int = BulletSpan.STANDARD_GAP_WIDTH,
    @ColorInt color: Int? = null,
    builderAction: SpannableScope.() -> Unit
): SpannableScope =
    inSpans(if (color == null) BulletSpan(gapWidth) else BulletSpan(gapWidth, color), builderAction)

 fun SpannableScope.quote(
    @ColorInt color: Int? = null,
    builderAction: SpannableScope.() -> Unit
): SpannableScope =
    inSpans(if (color == null) QuoteSpan() else QuoteSpan(color), builderAction)

fun SpannableReplaceAtScope.quote(
    @ColorInt color: Int? = null
) {
    spannableScope[startIndex, endIndex] = if (color == null) QuoteSpan() else QuoteSpan(color)
}

fun SpannableScope.image(
    drawable: Drawable,
    width: Int = drawable.intrinsicWidth,
    height: Int = drawable.intrinsicHeight
): SpannableScope {
    drawable.setBounds(0, 0, width, height)
    return inSpans(ImageSpan(drawable)) { append(IMAGE_SPAN_TEXT) }
}

fun SpannableReplaceAtScope.image(
    drawable: Drawable,
    width: Int = drawable.intrinsicWidth,
    height: Int = drawable.intrinsicHeight){
    drawable.setBounds(0, 0, width, height)
    spannableScope[startIndex, endIndex] = ImageSpan(drawable)
}

fun SpannableScope.image(
    @DrawableRes resourceId: Int,
    context: Context = application
): SpannableScope = inSpans(ImageSpan(context, resourceId)) { append(IMAGE_SPAN_TEXT) }

fun SpannableReplaceAtScope.image(
    @DrawableRes resourceId: Int,
    context: Context = application){
    spannableScope[startIndex, endIndex] = ImageSpan(context, resourceId)
}

fun SpannableScope.image(
    bitmap: Bitmap,
    context: Context = application
): SpannableScope = inSpans(ImageSpan(context, bitmap)) { append(IMAGE_SPAN_TEXT) }

fun SpannableReplaceAtScope.image(
    bitmap: Bitmap,
    context: Context = application){
    spannableScope[startIndex, endIndex] = ImageSpan(context, bitmap)
}

fun SpannableScope.clickable(
    text: CharSequence?,
    @ColorInt color: Int? = null,
    isUnderlineText: Boolean = true,
    onClick: (View) -> Unit
): SpannableScope = inSpans(ClickableSpan(color, isUnderlineText, onClick)) { append(text) }

fun SpannableReplaceAtScope.clickable(
    @ColorInt color: Int? = null,
    isUnderlineText: Boolean = true,
    onClick: (View) -> Unit){
    spannableScope[startIndex, endIndex] = ClickableSpan(color, isUnderlineText, onClick)
}

fun SpannableScope.clickable(
    drawable: Drawable,
    width: Int = drawable.intrinsicWidth,
    height: Int = drawable.intrinsicHeight,
    onClick: (View) -> Unit
): SpannableScope = inSpans(ClickableSpan(onClick = onClick)) { image(drawable, width, height) }

fun SpannableScope.clickable(
    @DrawableRes resourceId: Int,
    context: Context = application,
    onClick: (View) -> Unit
): SpannableScope = inSpans(ClickableSpan(onClick = onClick)) { image(resourceId, context) }

fun SpannableScope.clickable(
    bitmap: Bitmap,
    context: Context = application,
    onClick: (View) -> Unit
): SpannableScope = inSpans(ClickableSpan(onClick = onClick)) { image(bitmap, context) }


fun ClickableSpan(
    @ColorInt color: Int? = null,
    isUnderlineText: Boolean = true,
    onClick: (View) -> Unit,
): ClickableSpan = object : ClickableSpan() {
    override fun onClick(widget: View) = onClick(widget)

    override fun updateDrawState(ds: TextPaint) {
        ds.color = color ?: ds.linkColor
        ds.isUnderlineText = isUnderlineText
    }
}

/**
 * 占位符
 */
fun SpannableScope.suggestion(
    context: Context= application,
    suggestions: Array<String>,
    flags: Int = SuggestionSpan.FLAG_EASY_CORRECT or SuggestionSpan.FLAG_AUTO_CORRECTION,
    locale: Locale? = null,
    notificationTargetClass: Class<*>? = null,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(SuggestionSpan(context, locale, suggestions, flags, notificationTargetClass),builderAction)


fun SpannableReplaceAtScope.suggestion(
    context: Context= application,
    suggestions: Array<String>,
    flags: Int = SuggestionSpan.FLAG_EASY_CORRECT or SuggestionSpan.FLAG_AUTO_CORRECTION,
    locale: Locale? = null,
    notificationTargetClass: Class<*>? = null){
    spannableScope[startIndex, endIndex] = SuggestionSpan(context, locale, suggestions, flags, notificationTargetClass)
}

fun SpannableScope.drawableMargin(
    drawable: Drawable,
    padding:Int=0,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(DrawableMarginSpan(drawable,padding),builderAction)


inline fun SpannableScope.inLine(
    builderAction: SpannableScope.() -> Unit
): SpannableScope {
    builderAction()
    newLineAble()
    return this
}

@RequiresApi(Build.VERSION_CODES.Q)
fun SpannableScope.lineBackground(
    color:Int=Color.BLUE,
    builderAction: SpannableScope.() -> Unit
): SpannableScope = inSpans(LineBackgroundSpan.Standard(color), builderAction)

@RequiresApi(Build.VERSION_CODES.Q)
fun SpannableReplaceAtScope.lineBackground(
    color:Int=Color.BLUE
) {
    spannableScope[startIndex, endIndex] = LineBackgroundSpan.Standard(color)
}

fun SpannableBlockScope.newLineAble() {
    isNewLine().isFalse {
        newLine()
    }
}

fun SpannableBlockScope.newLine() {
    append("\n")
}

fun SpannableBlockScope.br() {
    append("\n")
}

fun SpannableBlockScope.isNewLine():Boolean {
    if(length-1>0) {
        return get(length-1).equals('\n',true)
    }
    return false
}

/**
 * 扩展Spanned +, 保留样式
 * operator [Spannable] + [CharSequence]
 * @return [Spannable]
*/
operator fun Spanned.plus(other: String): SpannableStringBuilder =
    when (this) {
        is SpannableStringBuilder -> append(other)
        else -> SpannableStringBuilder(this).append(other)
    }

operator fun Spanned.plus(other: SpannedString): SpannableStringBuilder =
    when (this) {
        is SpannableStringBuilder -> append(other)
        else -> SpannableStringBuilder(this).append(other)
    }

interface SpannableBaseScope: CharSequence,Spannable{

    //fun setSpan(what: Any?, start: Int, end: Int, flags: Int)
    fun inSpans(
    vararg spans: Any,
    builderAction: SpannableScope.() -> Unit
    ): SpannableScope

    fun inSpans(
        span: Any,
        builderAction: SpannableScope.() -> Unit
    ): SpannableScope

    fun append(text: CharSequence?): SpannableStringBuilder

}

interface SpannableTextScope{
    fun text(text: String): SpannableScope
}

interface SpannableBlockScope: SpannableTextScope, SpannableBaseScope {

}

interface SpannableScope: SpannableBaseScope, SpannableReplaceScope, SpannableBlockScope {

}

class SpannableStringBuilderAdv(val base:CharSequence="",start:Int=0, end:Int=base.length): SpannableStringBuilder(base, start, end),
    SpannableScope {


    /**
     * Wrap appended text in [builderAction] in [spans].
     *
     * Note: the spans will only have the correct position if the [builderAction] only appends or
     * replaces text. Inserting, deleting, or clearing the text will cause the span to be placed at
     * an incorrect position.
     */
    override fun inSpans(
        vararg spans: Any,
        builderAction: SpannableScope.() -> Unit
    ): SpannableScope {
        val start = length
        builderAction()
        for (span in spans) setSpan(span, start, length, SPAN_INCLUSIVE_EXCLUSIVE)
        return this
    }

    /**
     * Wrap appended text in [builderAction] in [span].
     *
     * Note: the span will only have the correct position if the `builderAction` only appends or
     * replaces text. Inserting, deleting, or clearing the text will cause the span to be placed at
     * an incorrect position.
     */
    override fun inSpans(
        span: Any,
        builderAction: SpannableScope.() -> Unit
    ): SpannableScope {
        val start = length
        builderAction()
        setSpan(span, start, length, SPAN_INCLUSIVE_EXCLUSIVE)
        return this
    }

    override fun at(start: Int, end: Int, spans: SpannableReplaceAtScope.() -> Unit) {
        if(start<0 || start>=end) return

         val obj=object: SpannableReplaceAtScope {
             override val startIndex: Int = start
             override val endIndex: Int = end
             override val spannableScope: SpannableBlockScope = this@SpannableStringBuilderAdv
         }

        spans(obj)
    }

    override fun at(vararg strList: String,start: Int, spans: SpannableReplaceAtScope.() -> Unit) {
        strList.forEach {str->
            base.indexOf(str,).takeIf { it != -1 }?.let { startIndex ->
                at(startIndex, startIndex + str.length,spans)
            }
        }
    }

    override fun setOrReplaceSpan(what: Any, start: Int, end: Int, flags: Int) {
        when (what) {
            is Array<*> -> what.forEach {
                if (it == null) return@forEach
                val existSpan = getSpans(start, end, it::class.java).getOrNull(0)
                if (existSpan == null) {
                    setSpan(it, start, end, flags)
                } else {
                    if (getSpanStart(existSpan) != start || getSpanEnd(existSpan) != end) {
                        removeSpan(existSpan)
                        setSpan(it, start, end, flags)
                    }
                }
            }
            is List<*> -> what.forEach {
                if (it == null) return@forEach
                val existSpan = getSpans(start, end, it::class.java).getOrNull(0)
                if (existSpan == null) {
                    setSpan(it, start, end, flags)
                } else {
                    if (getSpanStart(existSpan) != start || getSpanEnd(existSpan) != end) {
                        removeSpan(existSpan)
                        setSpan(it, start, end, flags)
                    }
                }
            }
            else -> {
                val existSpan = getSpans(start, end, what::class.java).getOrNull(0)
                if (existSpan == null) {
                    setSpan(what, start, end, flags)
                } else {
                    if (getSpanStart(existSpan) != start || getSpanEnd(existSpan) != end) {
                        removeSpan(existSpan)
                        setSpan(what, start, end, flags)
                    }
                }
            }
        }
    }

    override fun replaceSpan(
        regex: Regex,
        startIndex: Int,
        endIndex: Int?,
        quoteGroup: Boolean,
        replacement: (MatchResult) -> Any?
    ) {
        val sequence = regex.findAll(base, startIndex)
        val count = sequence.count()
        if (count == 0) return
        var offset = 0

        loop {
            sequence.forEach { matchResult ->
                val range = matchResult.range

                endIndex?.let {
                    if (range.first >= it)
                        return@loop
                }

                replacement(matchResult)?.let { spanned ->
                    when (spanned) {
                        is List<*> -> for (item in spanned) {
                            setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                        }

                        is Array<*> -> for (item in spanned) {
                            setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                        }

                        is CharSequence -> {
                            var adjustReplacement: CharSequence = spanned
                            val groups = matchResult.destructured.toList()
                            if (quoteGroup && groups.isNotEmpty()) {
                                val attachedSpans = if (spanned is Spanned) {
                                    spanned.getSpans(0, spanned.length, Any::class.java)
                                } else null
                                groups.forEachIndexed { index, s ->
                                    val groupRegex = "\\$$index".toRegex()
                                    if (adjustReplacement.contains(groupRegex)) {
                                        adjustReplacement = adjustReplacement.replace(groupRegex, s)
                                    }
                                }
                                if (attachedSpans != null && adjustReplacement !== spanned) {
                                    attachedSpans.forEach {
                                        if (adjustReplacement is Spannable) {
                                            setOrReplaceSpan(it, end = length)
                                        } else {
                                            adjustReplacement =
                                                SpannableStringBuilder(adjustReplacement).apply {
                                                    setOrReplaceSpan(it, end = length)
                                                }
                                        }
                                    }
                                }
                            }
                            val matchLength = matchResult.value.length
                            replace(
                                range.first + offset,
                                range.first + offset + matchLength,
                                adjustReplacement
                            )
                            offset += adjustReplacement.length - matchLength
                        }

                        else -> setOrReplaceSpan(spanned, range.first, range.last + 1)
                    }
                }
            }
        }
    }

    override fun replaceSpanFirst(
        regex: Regex,
        quoteGroup: Boolean,
        startIndex: Int,
        replacement: (MatchResult) -> Any?
    ) {
        val matchResult = regex.find(base, startIndex) ?: return
        val range = matchResult.range
        replacement(matchResult)?.let { spanned ->
            when (spanned) {
                is List<*> -> for (item in spanned) {
                    setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                }
                is Array<*> -> for (item in spanned) {
                    setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                }
                is CharSequence -> {
                    var adjustReplacement: CharSequence = spanned
                    val groups = matchResult.destructured.toList()
                    if (quoteGroup && groups.isNotEmpty()) {
                        val attachedSpans = if (spanned is Spanned) {
                            spanned.getSpans(0, spanned.length, Any::class.java)
                        } else null
                        groups.forEachIndexed { index, s ->
                            val groupRegex = "\\$$index".toRegex()
                            if (adjustReplacement.contains(groupRegex)) {
                                adjustReplacement = adjustReplacement.replace(groupRegex, s)
                            }
                        }
                        if (attachedSpans != null && adjustReplacement !== spanned) {
                            attachedSpans.forEach {
                                if (adjustReplacement is Spannable) {
                                    setOrReplaceSpan(it,end=length)
                                } else {
                                    adjustReplacement = SpannableStringBuilder(adjustReplacement).apply {
                                        setOrReplaceSpan(it,end=length)
                                    }
                                }
                            }
                        }
                    }
                    val matchLength = matchResult.value.length
                    replace(range.first, range.first + matchLength, adjustReplacement)
                }
                else -> setOrReplaceSpan(spanned, range.first, range.last + 1)
            }
        }
    }

    override fun replaceSpanLast(
        regex: Regex,
        quoteGroup: Boolean,
        startIndex: Int,
        replacement: (MatchResult) -> Any?
    ) {
        val matchResult = regex.findAll(base, startIndex).lastOrNull() ?: return
        val range = matchResult.range
        replacement(matchResult)?.let { spanned ->
            when (spanned) {
                is List<*> -> for (item in spanned) {
                    setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                }
                is Array<*> -> for (item in spanned) {
                    setOrReplaceSpan(item ?: continue, range.first, range.last + 1)
                }
                is CharSequence -> {
                    var adjustReplacement: CharSequence = spanned
                    val groups = matchResult.destructured.toList()
                    if (quoteGroup && groups.isNotEmpty()) {
                        val attachedSpans = if (spanned is Spanned) {
                            spanned.getSpans(0, spanned.length, Any::class.java)
                        } else null
                        groups.forEachIndexed { index, s ->
                            val groupRegex = "\\$$index".toRegex()
                            if (adjustReplacement.contains(groupRegex)) {
                                adjustReplacement = adjustReplacement.replace(groupRegex, s)
                            }
                        }
                        if (attachedSpans != null && adjustReplacement !== spanned) {
                            attachedSpans.forEach {
                                if (adjustReplacement is Spannable) {
                                    setOrReplaceSpan(it,end=length)
                                } else {
                                    adjustReplacement = SpannableStringBuilder(adjustReplacement).apply {
                                        setOrReplaceSpan(it,end=length)
                                    }
                                }
                            }
                        }
                    }
                    val matchLength = matchResult.value.length
                    replace(range.first, range.first + matchLength, adjustReplacement)
                }
                else -> setOrReplaceSpan(spanned, range.first, range.last + 1)
            }
        }
    }

    override fun appendSpan(builderConfig: SpannableBlockScope.() -> Unit) {
        builderConfig(this)
    }

    override fun text(text: String): SpannableScope {
        append(text)
        return this
    }
}

fun formatJson(json: String): String {
    try {
        return JSONObject(json).toString(4)
    } catch (e: Exception) {
        Timber.d("format json error:$e")
    }
    return json
}